//
//  ECDSA.swift
//
//  Copyright (c) 2016-present, LY Corporation. All rights reserved.
//
//  You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
//  copy and distribute this software in source code or binary form for use
//  in connection with the web services and APIs provided by LY Corporation.
//
//  As with any software that integrates with the LY Corporation platform, your use of this software
//  is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement].
//  This copyright notice shall be included in all copies or substantial portions of the software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
//  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import Foundation
import CommonCrypto

/// Namespace for ECDSA related things.
struct ECDSA {}

/// ECDSA Digest Algorithms.
extension ECDSA {
    enum Curve: String, Decodable {
        case P256 = "P-256"
        case P384 = "P-384"
        case P521 = "P-521"
        
        var signatureOctetLength: Int {
            return coordinateOctetLength * 2
        }
        
        // Standards for Efficient Cryptography Group SEC 1:
        // Elliptic Curve Cryptography
        // http://www.secg.org/sec1-v2.pdf
        var coordinateOctetLength: Int {
            switch self {
            case .P256:
                return 32
            case .P384:
                return 48
            case .P521:
                return 66
            }
        }
    }
    
    enum Algorithm: CryptoAlgorithm {

        case sha1, sha224, sha256, sha384, sha512
        
        var signatureAlgorithm: SecKeyAlgorithm {
            switch self {
            case .sha1:   return .ecdsaSignatureMessageX962SHA1
            case .sha224: return .ecdsaSignatureMessageX962SHA224
            case .sha256: return .ecdsaSignatureMessageX962SHA256
            case .sha384: return .ecdsaSignatureMessageX962SHA384
            case .sha512: return .ecdsaSignatureMessageX962SHA512
            }
        }

        var digestAlgorithm: DigestAlgorithm {
            switch self {
            case .sha1:   return .sha1
            case .sha224: return .sha224
            case .sha256: return .sha256
            case .sha384: return .sha384
            case .sha512: return .sha384
            }
        }
        
        var encryptionAlgorithm: SecKeyAlgorithm {
            Log.fatalError("ECDSA should be only used for signing purpose.")
        }

        func convertSignatureData(_ data: Data) throws -> Data {

            // EC algorithm does not work seamlessly when using iOS 10 SecKeyVerifySignature and related
            // SecKeyAlgorithm. It is due to JWT contains raw R|S instead of encoded one.
            // .ecdsaSignatureMessageX962 algorithm supports an ASN.1 DER x96.2 encoded signature
            // instead of plain {r, s} signature (which is defined in ANSI X9.62, FIPS 186-4).
            // So we need to convert the signature before use it to verify.
            //
            // See http://www.secg.org/sec1-v2.pdf
            //     https://crypto.stackexchange.com/questions/57731/ecdsa-signature-rs-to-asn1-der-encoding-question
            //     https://opensource.apple.com/source/Security/Security-57740.51.3/keychain/SecKey.h
            // More discussion:
            // https://forums.developer.apple.com/thread/83136 (ECDSA signature generated by OpenSSL)
            // or https://forums.developer.apple.com/thread/89619
            //
            return try DEREncodedSignatureData(data)
        }

        func DEREncodedSignatureData(_ data: Data) throws -> Data {

            // The EC raw signature data is {r,s}. So it must be two int with equal length.
            let count = data.count
            guard count != 0 && count % 2 == 0 else {
                throw CryptoError.algorithmsFailed(reason: .invalidSignature(data: data))
            }

            // Following RFC 5480 (https://www.ietf.org/rfc/rfc5480.txt) to encode {r, s} as:
            //
            // ECDSA-Sig-Value ::= SEQUENCE {
            //   r  INTEGER,
            //   s  INTEGER
            // }
            // https://tools.ietf.org/html/rfc3278#section-8.2
            //
            var rBytes = [UInt8](data[..<(count / 2)])
            var sBytes = [UInt8](data[(count / 2)...])

            // For byte >= 0x80 (first bit is 1), prefix 0x00 to make Security framework happy.
            if rBytes.first! >= 0x80 {
                rBytes.insert(0x00, at: 0)
            }

            if sBytes.first! >= 0x80 {
                sBytes.insert(0x00, at: 0)
            }

            let bytes = (rBytes.encode(as: .integer) + sBytes.encode(as: .integer)).encode(as: .sequence)
            
            #if swift(>=5.0)
            return Data(bytes)
            #else
            return Data(bytes: bytes)
            #endif

        }
    }
}
